package com.netforce.data.source;

import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.http.client.utils.URIBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

import com.netforce.data.asserts.DataException;
import com.netforce.util.convert.Converter;
import com.netforce.util.convert.ConverterException;
import com.netforce.util.convert.FromJsonConverter;
import com.netforce.util.io.IoManager;

public interface DataSource
{
  public InputStream authenticate() throws DataException;
  public Connection connect(String aCode) throws DataException;

  public class DefaultDataSource implements DataSource
  {
    public DefaultDataSource(String anAuthorizationUrl, String anAccessTokenUrl, String aClientId, String aClientSecret, String aRedirectUrl)
    {
      authorizationUrl = anAuthorizationUrl;
      accessTokenUrl = anAccessTokenUrl;
      clientId = aClientId;
      clientSecret = aClientSecret;
      redirectUrl = aRedirectUrl;
    }

    @Override
    public InputStream authenticate() throws DataException
    {
      try
      {
        URIBuilder uriBuilder = new URIBuilder(authorizationUrl);
        uriBuilder.setParameter(RESPONSE_TYPE_PARAMETER_NAME, CODE_PARAMETER_VALUE);
        uriBuilder.setParameter(CLIENT_ID_PARAMETER_NAME, clientId);
        uriBuilder.setParameter(REDIRECT_URI_PARAMETER_NAME, redirectUrl);
        GetMethod getMethod = new GetMethod(uriBuilder.build().toString());

        HttpClient httpClient = new HttpClient();
        httpClient.executeMethod(getMethod);
        return getMethod.getResponseBodyAsStream();
      }
      catch (IOException | URISyntaxException anException)
      {
        throw new DataException("Unable to authorize", anException);
      }
    }

    @Override
    public Connection connect(String aCode) throws DataException
    {
      try
      {
        PostMethod postMethod = new PostMethod(accessTokenUrl);
        postMethod.addParameter(GRANT_TYPE_PARAMETER_NAME, AUTHORIZATION_CODE_PARAMETER_VALUE);
        postMethod.addParameter(CLIENT_ID_PARAMETER_NAME, clientId);
        postMethod.addParameter(CLIENT_SECRET_PARAMETER_NAME, clientSecret);
        postMethod.addParameter(REDIRECT_URI_PARAMETER_NAME, redirectUrl);
        postMethod.addParameter(CODE_PARAMETER_NAME, aCode);

        HttpClient httpClient = new HttpClient();
        httpClient.executeMethod(postMethod);
        String content = IoManager.toString(postMethod.getResponseBodyAsStream());
        AccessTokenInputMessage accessTokenInputMessage = new FromJsonConverter<>(AccessTokenInputMessage.class).convert(content);
        return new DataSourceConnection(accessTokenInputMessage.getAccessToken(), accessTokenInputMessage.getInstanceUrl());
      }
      catch (IOException | IllegalStateException | ConverterException anException)
      {
        throw new DataException("Unable to access token with code " + aCode, anException);
      }
    }

    protected String authorizationUrl;
    protected String accessTokenUrl;
    protected String clientId;
    protected String clientSecret;
    protected String redirectUrl;


    @Autowired
    @Qualifier("InputStreamToStringConverter")
    protected Converter<InputStream, String> InputStreamToStringConverter;
    @Autowired
    protected IoManager IoManager;


    protected class DataSourceConnection implements Connection
    {
      public DataSourceConnection(String aToken, String aUrl)
      {
        token = aToken;
        url = aUrl;
      }

      @Override
      public InputStream httpGet(String aService) throws DataException
      {
        try
        {
          URIBuilder uriBuilder = new URIBuilder(url + aService);
          GetMethod getMethod = new GetMethod(uriBuilder.build().toString());
          getMethod.setRequestHeader("Authorization", "Bearer " + token);

          HttpClient httpClient = new HttpClient();
          httpClient.executeMethod(getMethod);
          return getMethod.getResponseBodyAsStream();
        }
        catch (IOException | URISyntaxException anException)
        {
          throw new DataException("Unable to get service " + aService, anException);
        }
      }

      @Override
      public <Type> Type httpGet(Class<Type> aClass, String aService) throws DataException
      {
        try
        {
          String content = IoManager.toString(httpGet(aService));
          return new FromJsonConverter<>(aClass).convert(content);
        }
        catch (IOException | ConverterException anException)
        {
          throw new DataException("Unable to get object type " + aClass.getName() + " from service " + aService, anException);
        }
      }

      protected String token;
      protected String url;
    }


    static protected final String RESPONSE_TYPE_PARAMETER_NAME = "response_type";
    static protected final String CODE_PARAMETER_VALUE = "code";
    static protected final String GRANT_TYPE_PARAMETER_NAME = "grant_type";
    static protected final String AUTHORIZATION_CODE_PARAMETER_VALUE = "authorization_code";
    static protected final String CLIENT_ID_PARAMETER_NAME = "client_id";
    static protected final String CLIENT_SECRET_PARAMETER_NAME = "client_secret";
    static protected final String REDIRECT_URI_PARAMETER_NAME = "redirect_uri";
    static protected final String CODE_PARAMETER_NAME = "code";
  }
}
